//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.Activities
{
    #region Using Directives

    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Collections;
    using System.Linq;
    using System.Workflow.ComponentModel;
    using System.Workflow.ComponentModel.Design;
    using System.Workflow.ComponentModel.Compiler;
    using System.Workflow.ComponentModel.Serialization;
    using System.Workflow.Runtime;
    using System.Workflow.Activities;
    using System.Workflow.Activities.Rules;
    using System.Diagnostics;
    using System.IO;

    using Microsoft.Research.ScientificWorkflow;
    using Microsoft.Research.ScientificWorkflow.Helpers;
    using Microsoft.Research.ScientificWorkflow.NetCDF.CSharpAPI;
    using System.Collections.Generic;
    using WorkflowData;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    
    #endregion

    #region HyperCubeSchemaGeneratorActivity

    /// <summary>
    /// The Hyper Cube Schema Generator class will generate a Hyper Cube Schema object as
    /// an output property. The comma seperated list of variables is the list of variables
    /// for which the values will be stored at the datapoints.
    /// </summary>
    [Name("Hyper Cube Schema Generator")]
    [Description("Generates Hyper Cube Schema Object")]
    [WorkflowCategory("NetCdf")]
    [Designer(typeof(HyperCubeSchemaGeneratorDesigner), typeof(IDesigner))]
    public class HyperCubeSchemaGeneratorActivity : Activity
    {
        #region Dependency Properties

        // Required input properties
        public static DependencyProperty NcFileNameProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("NcFileName", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty VariablesProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("Variables", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty XDimNameProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("XDimName", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty YDimNameProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("YDimName", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty ZDimNameProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("ZDimName", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty TDimNameProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("TDimName", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty XDimTypeProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("XDimType", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty YDimTypeProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("YDimType", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty ZDimTypeProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("ZDimType", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty TDimTypeProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("TDimType", typeof(string),
            typeof(HyperCubeSchemaGeneratorActivity));

        // Output Properties
        public static DependencyProperty NetCDFReaderProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("NetCDFReader", typeof(NetCDFReader),
            typeof(HyperCubeSchemaGeneratorActivity));

        public static DependencyProperty HyperCubeSchemaProperty =
            System.Workflow.ComponentModel.DependencyProperty.Register("HyperCubeSchema", typeof(HyperCubeSchema),
            typeof(HyperCubeSchemaGeneratorActivity));

        // Required input properties
        [RequiredInputParam]
        [Name("File Name", "Nc File to Load")]
        [Description("Name of the .nc file to be read")]
        public string NcFileName
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.NcFileNameProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.NcFileNameProperty, value);
            }
        }

        [RequiredInputParam]
        [Name("Variable Names", "Variable Names to be Stored")]
        [Description("List of variable names which will stoed in the data points")]
        public string Variables
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.VariablesProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.VariablesProperty, value);
            }
        }

        [RequiredInputParam]
        [Name("X Dimension Name")]
        [Description("Name of the variable mapped to the X dimension.")]
        public string XDimName
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.XDimNameProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.XDimNameProperty, value);
            }
        }

        [RequiredInputParam]
        [Name("Y Dimension Name")]
        [Description("Name of the variable mapped to the Y dimension.")]
        public string YDimName
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.YDimNameProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.YDimNameProperty, value);
            }
        }

        [OptionalInputParam]
        [Name("Z Dimension Name")]
        [Description("Name of the variable mapped to the Z dimension.")]
        public string ZDimName
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.ZDimNameProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.ZDimNameProperty, value);
            }
        }

        [RequiredInputParam]
        [Name("T Dimension Name")]
        [Description("Name of the variable mapped to the X dimension.")]
        public string TDimName
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.TDimNameProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.TDimNameProperty, value);
            }
        }

        [RequiredInputParam]
        [Name("X Dimension Type")]
        [Description("Type of the variable mapped to the X dimension.")]
        public string XDimType
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.XDimTypeProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.XDimTypeProperty, value);
            }
        }

        [RequiredInputParam]
        [Name("Y Dimension Type")]
        [Description("Type of the variable mapped to the Y dimension.")]
        public string YDimType
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.YDimTypeProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.YDimTypeProperty, value);
            }
        }

        [OptionalInputParam]
        [Name("Z Dimension Type")]
        [Description("Type of the variable mapped to the Z dimension.")]
        public string ZDimType
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.ZDimTypeProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.ZDimTypeProperty, value);
            }
        }

        [RequiredInputParam]
        [Name("T Dimension Type")]
        [Description("Type of the variable mapped to the X dimension.")]
        public string TDimType
        {
            get
            {
                return ((string)(base.GetValue(HyperCubeSchemaGeneratorActivity.TDimTypeProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.TDimTypeProperty, value);
            }
        }

        // Output Properties
        [OutputParam]
        [Name("NetCDF Reader Object")]
        [Description("Contains the NcFile Object containing .nc file metadata")]
        public NetCDFReader NetCDFReader
        {
            get
            {
                return ((NetCDFReader)(base.GetValue(HyperCubeSchemaGeneratorActivity.NetCDFReaderProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.NetCDFReaderProperty, value);
            }
        }

        [OutputParam]
        [Name("Schema Object", "Generated Schema")]
        [Description("Contains the Schema of the HyperCube based on selected variables")]
        public HyperCubeSchema HyperCubeSchema
        {
            get
            {
                return ((HyperCubeSchema)(base.GetValue(HyperCubeSchemaGeneratorActivity.HyperCubeSchemaProperty)));
            }
            set
            {
                base.SetValue(HyperCubeSchemaGeneratorActivity.HyperCubeSchemaProperty, value);
            }
        }

        #endregion

        #region Overridden Methods

        /// <summary>
        /// Creates the HyperCube Schema after verifying if the specified variables
        /// exist in the netCDF file.
        /// </summary>
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            this.NetCDFReader = NetCDFReader.Create(this.NcFileName);
            Debug.Assert(this.NetCDFReader != null, "Unable to create the ncFile Object");

            ParseVariables();
            ValidateVariables();

            HyperCubeSchema schema = new HyperCubeSchema(this.varArray.Length);

            AssignDimensions(ref schema);
            
            for (int idxVariable = 0; idxVariable < this.varArray.Length; idxVariable++)
            {
                schema.SetName(idxVariable, this.varArray[idxVariable].VariableName);
                schema.SetType(idxVariable, this.varArray[idxVariable].ClrType);
                schema.SetDescription(0, string.Empty);

                // Store the dimensions and attributes for variables.
                StoreDimensions(idxVariable, this.varArray[idxVariable].VariableName, schema);
                StoreAttributes(idxVariable, this.varArray[idxVariable].VariableName, schema);
            }

            this.HyperCubeSchema = schema;
            Debug.Assert(HyperCubeSchema != null, "Unable to generate the HyperCubeSchema object");

            return ActivityExecutionStatus.Closed;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// This method reads the attribues for the variable and stores the values
        /// for the attributes in the schema object.
        /// </summary>
        private void StoreAttributes(int idxVariable, string variableName, HyperCubeSchema schema)
        {
            NcMetaData meta = NetCDFFile.GetMetaData(this.NetCDFReader);

            NcVar var = meta.GetVariable(variableName);
            if (null == var)
                throw new ArgumentException("Incorrect Variable Name for setting the attribute.");

            foreach(NcAtt att in var.VariableAttributes)
            {
                schema.AddAttribute(idxVariable, att.Name, att.Value.DataValue);
            }
        }

        /// <summary>
        /// This method sets the dimensions agains which the variable changes in the schema
        /// object.
        /// </summary>
        private void StoreDimensions(int idxVariable, string variableName, HyperCubeSchema schema)
        {
            INetCDFVariable var = null;
            this.NetCDFReader.Variables.TryGetValue(variableName, out var);

            if (null == var)
                throw new ArgumentException("Invalid varaible name in Schema Generator: Variable List");

            for (int i = 0; i < var.DimensionIDs.Length; ++i)
            {
                INetCDFDimension dim = null;
                this.NetCDFReader.Dimensions.TryGetValue(var.DimensionIDs[i], out dim);

                if (null == dim)
                    throw new ArgumentException("Data not correctly parsed from netCDF");

                if (schema.XDimensionName == dim.Name)
                    schema.SetDimension(idxVariable, HyperCube.Axis.X);
                else if (schema.YDimensionName == dim.Name)
                    schema.SetDimension(idxVariable, HyperCube.Axis.Y);
                else if (schema.ZDimensionName == dim.Name)
                    schema.SetDimension(idxVariable, HyperCube.Axis.Z);
                else if (schema.TDimensionName == dim.Name)
                    schema.SetDimension(idxVariable, HyperCube.Axis.T);
            }
        }

        /// <summary>
        /// This method verifies if the dimension exits and if the type
        /// for the dimension matches with the type value passed.
        /// </summary>
        /// <returns></returns>
        private bool IsDimensionCorrect(string dimName, Type dimType)
        {
            if (String.IsNullOrEmpty(dimName))
                return true;

            foreach(KeyValuePair<uint, INetCDFDimension> kvp in this.NetCDFReader.Dimensions)
            {
                INetCDFDimension dim = kvp.Value as INetCDFDimension;
                if(dimName == dim.Name)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// This methods validates the values for the dimension names and type properties
        /// in the netCDF file. If found it assigns the same to the schema object.
        /// </summary>
        private void AssignDimensions(ref HyperCubeSchema schema)
        {
            Type xType = TypeMapper.GetClrType(this.XDimType);
            Type yType = TypeMapper.GetClrType(this.YDimType);
            Type zType = TypeMapper.GetClrType(this.ZDimType);
            Type tType = TypeMapper.GetClrType(this.TDimType);

            int counter = 0;
            if(IsDimensionCorrect(this.XDimName, xType))
            {
                schema.XDimensionName = this.XDimName;
                schema.XDimensionType = xType;
                counter+=1;
            }
            if (IsDimensionCorrect(this.YDimName, yType))
            {
                schema.YDimensionName = this.YDimName;
                schema.YDimensionType = yType;
                counter += 1;
            }
            if (IsDimensionCorrect(this.ZDimName, zType))
            {
                schema.ZDimensionName = this.ZDimName;
                schema.ZDimensionType = zType;
                counter += 1;
            }
            if (IsDimensionCorrect(this.TDimName, tType))
            {
                schema.TDimensionName = this.TDimName;
                schema.TDimensionType = tType;
                counter += 1;
            }

            if (4 != counter)
                throw new ArgumentException("Incorrect values for dimension");
        }

        /// <summary>
        /// This method splits the comman seperated variable name, type string in 
        /// and stored the same in the member variable array.
        /// </summary>
        private void ParseVariables()
        {
            string[] v = (this.Variables).Split(new char[] { ',' });
            if (v.Length % 2 != 0)
                throw new ArgumentException("Error Converting from String to Variable Array");

            Variable[] varArray = new Variable[v.Length / 2];
            for (int i = 0, j = 0; i < v.Length; j++)
            {
                Variable var = new Variable();

                var.VariableName = v[i];
                var.VariableType = v[i + 1];
                varArray[j] = var;
                i = i + 2;
            }
            this.varArray = varArray;
        }

        /// <summary>
        /// This method verifies if the variables specified as input are present
        /// in the netCDF file. This method also verifies if the types specfifies
        /// for variables are consistent with the netCDF file.
        /// If there is any inconsistency this method throws an ArgumentException.
        /// </summary>
        private void ValidateVariables()
        {
            foreach (Variable var in this.varArray)
            {
                bool isInFile = false;

                Dictionary<string, INetCDFVariable>.Enumerator variableEnum
                    = this.NetCDFReader.Variables.GetEnumerator();
                while (variableEnum.MoveNext())
                {
                    KeyValuePair<string, INetCDFVariable> kvp = variableEnum.Current;

                    if (var.VariableName == kvp.Value.Name &&
                        TypeMapper.GetClrType(kvp.Value.DataType) == var.ClrType)
                    {
                        isInFile = true;
                    }
                }

                if (!isInFile)
                {
                    throw new ArgumentException(
                        "Variable " + var.VariableName + " with type " +
                        var.VariableType + " not found in file " + NcFileName);
                }
            }
        }

        #endregion

        #region Private Member Variables

        Variable[] varArray;

        #endregion

    }

    #endregion

    #region HyperCubeSchemaGeneratorDesigner

    [ActivityDesignerTheme(typeof(HyperCubeSchemaGeneratorDesignerTheme))]
    public class HyperCubeSchemaGeneratorDesigner : ActivityDesigner
    {
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }
    }

    #endregion

    #region HyperCubeSchemaGeneratorDesignerTheme

    public class HyperCubeSchemaGeneratorDesignerTheme : ActivityDesignerTheme
    {
        public HyperCubeSchemaGeneratorDesignerTheme(WorkflowTheme theme)
            : base(theme)
        {
            this.ForeColor = Color.FromArgb(0xff, 0, 0, 0);
            this.BorderColor = Color.FromArgb(0xff, 0x79, 0x73, 0xa5);
            this.BorderStyle = DashStyle.Solid;
            this.BackColorStart = Color.FromArgb(0xff, 0xff, 0xe8, 0xdf);
            this.BackColorEnd = Color.FromArgb(0xff, 0xff, 0xb3, 0x95);
            this.BackgroundStyle = LinearGradientMode.Horizontal;
        }

    }

    #endregion
}
